#include "Interpreter.hpp"

string combineTokens(list<Token>::iterator start, list<Token>::iterator end);
list<Token>::iterator wordSplit(list<Token> &tokens, list<Token>::iterator pos);
string tildeExpand(string text, Job *context);
string commandSubstitute(string command, Job *context);
string arithmeticSubstitute(string expr, Job *context);
string expandVar(string varname, Job *context);

void expandAndSplit(list<Token> &tokens, vector<string> &params, Job *context, bool allowAssignment)
{
	bool allowFilenameExpansion = true;
	
	//
	// Brace expansion
	// TODO
	//
	
	//
	// Partition the list of tokens according to where the separators are. Then
	// classify each piece according to whether or not it's an assignment.
	// Everything within an assignment is exempt from word splitting and filename
	// expansion, but not from any other expansion. A shell command may start
	// with assignments, which may be followed by a command (non-assignment
	// token). Everything after the command is not an assignment, and so is
	// subject to word splitting, *unless* the command is a special builtin
	// which takes assignment arguments, such as 'alias' or 'export'.
	//
	// Assignments look like FOO=asdf. An assignment is bounded at the beginning
	// by a TOK_SEPARATOR or the start of the token list, and at the end by a
	// TOK_SEPARATOR or the end of the token list. It starts with a TOK_TEXT
	// that starts with "[_a-zA-Z][_a-zA-Z0-9]=".
	//
	list< list<Token> > pieces;
	list<Token>::iterator pieceIter;
	list<Token>::iterator start = tokens.begin();
	
	// Split into pieces
	for(pieceIter=tokens.begin(); pieceIter!=tokens.end(); pieceIter++)
	{
		if(pieceIter->type == TOK_SEPARATOR)
		{
			list<Token> piece;
			piece.insert(piece.end(), start, pieceIter);
			pieces.push_back(piece);
			start = pieceIter;
			start++;
		}
	}
	if(pieceIter != start) {
		list<Token> piece;
		piece.insert(piece.end(), start, pieceIter);
		pieces.push_back(piece);
	}
	
	for(list< list<Token> >::iterator ii=pieces.begin(); ii!=pieces.end(); ii++)
	{
		list<Token> &piece = *ii;
		bool assn = allowAssignment && isAssignment(piece);
		
		// If not an assignment, check if it's a builtin that affects the
		// expansion of parameters after it.
		if(!assn)
		{
			if(piece.size()==1 && piece.begin()->type==TOK_TEXT)
			{
				if(isBuiltin(piece.begin()->text))
				{
					if(!builtinTakesAssignments(piece.begin()->text))
						allowAssignment = false;
					if(builtinDisablesFileExpansion(piece.begin()->text))
						allowFilenameExpansion = false;
				}
			}
		}
		
		for(list<Token>::iterator jj=piece.begin(); jj!=piece.end(); )
		{
			bool expansionDone = false;
			
			// Command substitution
			if(jj->type == TOK_COMMAND_SUBSTITUTE)
			{
				*jj = Token(TOK_TEXT, commandSubstitute(jj->text, context).c_str(), jj->quoted);
				expansionDone = true;
			}
			// Arithmetic substitution
			else if(jj->type == TOK_ARITH_SUBSTITUTE)
			{
				*jj = Token(TOK_TEXT, arithmeticSubstitute(jj->text, context).c_str(), true);
				expansionDone = true;
			}
			// Variable substitution
			else if(jj->type == TOK_VAR)
			{
				// TODO: Some variables force their expansions to be quoted; handle
				// that here.
				string expansion = expandVar(jj->text, context);
				*jj = Token(TOK_TEXT, expansion.c_str(), jj->quoted);
				expansionDone = true;
			}
			
			// Word splitting (but not in assignments)
			if(expansionDone && !assn)
				jj = wordSplit(piece, jj);
			
			// Remove non-quoted tokens that expanded to empty
			if(jj->type==TOK_TEXT && !jj->text.length() && !jj->quoted)
			{
				jj = piece.erase(jj);
				continue;
			}
			// Tilde expansion
			else if(!jj->quoted)
			{
				if(jj==piece.begin() && jj->text.size()>0 && jj->text[0]=='~') {
					jj->text = tildeExpand(jj->text, context);
				}
				jj++;
			}
			else
			{
				jj++;
			}
		}
		
		//
		// Filename expansion and parameter consolidation
		//
		if(assn)
		{
			// If this is an assignment token, then there should be no
			// separators within this piece (since word splitting was
			// not done), and we don't do filename expansion, so just
			// catenate the tokens (which are all TOK_TEXT, quoted or
			// unquoted).
			string param = "";
			for(list<Token>::iterator jj=piece.begin(); jj!=piece.end(); jj++)
				param += jj->text;
			params.push_back(param);
		}
		else
		{
			piece.push_back(Token(TOK_SEPARATOR));
			list<Token>::iterator start = piece.begin();
			
			for(list<Token>::iterator jj=piece.begin(); jj!=piece.end(); jj++)
			{
				if(jj->type == TOK_SEPARATOR)
				{
					if(start==jj) {
						// Two separators in the list in a row -> empty
						// argument -> skip
					} else if(allowFilenameExpansion) {
						filenameExpand(start, jj, params, context);
					} else {
						params.push_back(combineTokens(start, jj));
					}
					start = jj;
					start++;
				}
			}
		}
	}
}

string combineTokens(list<Token>::iterator start, list<Token>::iterator end)
{
	string ret = "";
	for(list<Token>::iterator ii=start; ii!=end; ii++)
		ret += ii->text;
	return ret;
}

list<Token>::iterator wordSplit(list<Token> &tokens, list<Token>::iterator pos)
{
	if(pos->type != TOK_TEXT || pos->quoted) {
		return pos;
	}
	
	const char *textpos = pos->text.c_str();
	
	while(*textpos)
	{
		if(isSeparator(*textpos)) {
			while(isSeparator(*textpos))
				textpos++;
			
			if(*textpos) {
				tokens.insert(pos, Token(TOK_SEPARATOR));
			} else {
				pos->type = TOK_SEPARATOR;
				pos->text = "";
			}
		} else {
			const char *start = textpos;
			while(*textpos && !isSeparator(*textpos))
				textpos++;
			
			if(*textpos) {
				tokens.insert(pos, Token(TOK_TEXT, string(start, textpos-start).c_str(), false));
			} else {
				pos->text = string(start, textpos-start);
			}
		}
	}
	
	return pos;
}

string tildeExpand(string text, Job *context)
{
	if(!text.size())
		return "";
	if(text[0] != '~')
		return text;
	
	// Extract the portion of the text between the ~ and the first slash (or
	// up to end of the token)
	int end;
	for(end = 1; end<text.size(); end++) {
		if(text[end] == '/')
			break;
	}
	
	string tildesubstr = text.substr(1, end-1);
	
	// Could be just ~
	if(!tildesubstr.size()) {
		string homeDir = context->getState()->env.get("HOME");
		return homeDir + text.substr(end, text.length()-end);
	}
	// Could be '+' (PWD)
	if(tildesubstr == "+") {
		// TODO
	}
	// Could be '-' (OLDPWD, or '~-' if OLDPWD undefined)
	// TODO
	// Could be a username
	// TODO
	// Could be a directory stack entry (N, +N, or -N)
	// TODO
	
	return text;
}

string commandSubstitute(string command, Job *context)
{
	// TODO
	return "";
}

string arithmeticSubstitute(string expr, Job *context)
{
	// TODO
	return "";
}

string expandVar(string varname, Job *context)
{
	if(!varname.size()) {
		return "";
	} else if(varname[0]=='{') {
		if(varname.size() < 2) return "";
		varname = varname.substr(1, varname.length()-2);
		return context->getState()->env.get(varname);
	} else {
		return context->getState()->env.get(varname);
	}
}

//////////////////////////////////////////////////////////////////////////////

bool isAssignment(const list<Token> &tokens)
{
	if(!tokens.size())
		return false;
	
	const Token &firstToken = *tokens.begin();
	if(firstToken.type != TOK_TEXT)
		return false;
	
	return isAssignment(firstToken.text);
}

bool isAssignment(string param)
{
	for(int ii=0; ii<param.length(); ii++) {
		if(ii>0 && param[ii] == '=') {
			return true;
		} else {
			if(!isVarnameChar(param[ii], ii==0))
				return false;
		}
	}
	return false;
}

bool isVarnameChar(char ch, bool first)
{
	if(ch=='_') return true;
	else if(ch>='a'||ch<='z'||ch>='A'||ch<='Z') return true;
	else if(!first && ch>='0'&&ch<='9') return true;
	else if((unsigned char)ch>127) return true;
	else return false;
}

string namePart(string param)
{
	for(int ii=0; ii<param.length(); ii++)
		if(param[ii] == '=')
			return param.substr(0, ii);
	return "";
}

string valuePart(string param)
{
	for(int ii=0; ii<param.length(); ii++)
		if(param[ii] == '=')
			return param.substr(ii+1, param.length()-ii-1);
	return "";
}

